import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_isolate/easy_isolate.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart';
import 'package:sqflite_common/sqlite_api.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:uuid/uuid.dart';
import 'event_info.dart';
import 'flutter_tracker_platform_interface.dart';

typedef AppRunner = FutureOr<void> Function();

void mainHandler(dynamic data, SendPort isolateSendPort) {}

void _doWork(
    dynamic data, SendPort mainSendPort, SendErrorFunction onSendError) async {
  if (data is EventInfo) {
    FlutterTracker.instance._insert(data);
  } else {
    FlutterTracker.instance._dequeue();
  }
}

late final Database database;

class FlutterTracker {
  // 数据库版本号
  static const int _dbVersion = 10;
  // 数据库表名
  static const String _tableName = 'Event';

  static final FlutterTracker _instance = FlutterTracker();
  final uuid = const Uuid();
  static FlutterTracker get instance => _instance;
  String? curPage;
  String? prePage;
  late PackageInfo packageInfo;
  var deviceInfo;
  String? userId;
  String? clientId;
  String? deviceId;
  String? authorization;
  String? url;
  late Timer dequeueTimer;
  late int limit = 50;
  late String appId;
  late bool debug;
  int trackerCount = 0;
  var dio = Dio();
  final worker = Worker();

  Future<void> init({
    required String appId,
    required String deviceId,
    Duration interval = const Duration(seconds: 30),
    bool debug = false,
    int limit = 50,
    String url = "https://oapi.jarvisgo.cn/open/posLog/addBatch",
  }) async {
    this.limit = limit;
    this.appId = appId;
    this.debug = debug;
    this.deviceId = deviceId;
    this.url = url;
    if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
      sqfliteFfiInit();
      var databaseFactory = databaseFactoryFfi;
      var databasesPath = await databaseFactory.getDatabasesPath();
      String path = join(databasesPath, 'qws_tracker.db');
      database = await databaseFactory.openDatabase(path,
          options: OpenDatabaseOptions(
              version: 10,
              onCreate: (Database db, int version) async {
                await db.execute('''CREATE TABLE $_tableName (
                id INTEGER PRIMARY KEY, 
                appid TEXT, 
                app_name TEXT, 
                package_name TEXT, 
                trace_id TEXT, 
                event_id TEXT, 
                event_time INTEGER, 
                cost_time INTEGER, 
                event_type TEXT, 
                platform INTEGER, 
                user_id TEXT, 
                client_id TEXT, 
                device_id TEXT, 
                device_info TEXT, 
                app_version TEXT, 
                cur_page TEXT, 
                pre_page TEXT, 
                extra_info TEXT)''');
              }));
    } else {
      var databasesPath = await getDatabasesPath();
      String path = join(databasesPath, 'qws_tracker.db');
      database = await openDatabase(path, version: _dbVersion,
          onCreate: (Database db, int version) async {
        await db.execute(
            '''CREATE TABLE $_tableName (
            id INTEGER PRIMARY KEY, 
            appid TEXT, 
            app_name TEXT, 
            package_name TEXT, 
            trace_id TEXT, 
            event_id TEXT, 
            event_time INTEGER, 
            cost_time INTEGER, 
            event_type TEXT, 
            platform INTEGER, 
            user_id TEXT, 
            client_id TEXT, 
            device_id TEXT, 
            device_info TEXT, 
            app_version TEXT, 
            cur_page TEXT, 
            pre_page TEXT, 
            extra_info TEXT)''');
      });
    }
    await worker.init(mainHandler, _doWork, queueMode: true);
    dequeueTimer = Timer.periodic(interval, (_) {
      if (trackerCount > 0) {
        _dequeue();
      }
    });
    DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    deviceInfo = await deviceInfoPlugin.deviceInfo;
    packageInfo = await PackageInfo.fromPlatform();
    FlutterError.onError = (details) {
      event(
          traceId: uuid.v4(),
          eventId: "exception",
          eventType: "exception",
          extraInfo: details.exceptionAsString());
    };
    Future.sync(() {
      event(eventId: "run", eventType: "run", traceId: uuid.v4());
    });
    return;
  }

  void setUserInfo(String userId, String clientId, String authorization) {
    this.userId = userId;
    this.clientId = clientId;
    this.authorization = authorization;
  }

  Future<bool> _tracker(EventInfo info) async {
    if (debug) {
      print(info.toMap());
    }
    var start = DateTime.now().millisecondsSinceEpoch;
    await _insert(info);
    trackerCount++;
    if (trackerCount > limit) {
      trackerCount = 0;
      await _dequeue();
    }
    if (debug) {
      print("_tracker time : ${DateTime.now().millisecondsSinceEpoch - start}");
    }
    return true;
  }

  Future<bool> _dequeue() async {
    List<Map<String, Object?>> list = await database.query('Event');
    if (list.isEmpty) {
      if (debug) {
        print("empty database");
      }
      return false;
    }
    _report(list);
    return true;
  }

  Future<int> _insert(EventInfo info) async {
    try {
      return await database.transaction((txn) async {
        return await txn.insert(_tableName, info.toMap());
      });
    } catch (e) {
      if (debug) {
        print('Insert error: $e');
      }
      return -1;
    }
  }

  void _report(List<Map<String, Object?>> actions) async {
    var start = DateTime.now().millisecondsSinceEpoch;
    try {
      Response response = await dio.post(
        url ?? "https://oapi.jarvisgo.cn/open/posLog/addBatch",
        data: actions,
        options: Options(headers: {
          "Content-Type": "application/json",
          "Authorization": authorization,
          "deviceId": deviceId,
        }),
      );
      if (response.statusCode == 200 && response.data["code"] == "100000") {
        await _delete(actions);
        print("_upload success!!!");
        return;
      }
    } finally {
      print("upload time : ${DateTime.now().millisecondsSinceEpoch - start}");
    }
  }

  Future<int> _delete(List<Map<String, Object?>> actions) async {
    database.transaction((txn) async {
      for (var element in actions) {
        if (element['id'] != null) {
          await txn
              .delete('Event', where: "id = ?", whereArgs: [element['id']]);
        }
      }
    });
    return 1;
  }

  void _upload(dynamic data) async {
    var start = DateTime.now().millisecondsSinceEpoch;
    try {
      Response response = await dio.post(
        url ?? "https://oapi.jarvisgo.cn/open/posLog/addBatch",
        data: data,
        options: Options(headers: {
          "Content-Type": "application/json",
          "Authorization": authorization,
          "deviceId": deviceId,
        }),
      );
      if (response.statusCode == 200 && response.data["code"] == "100000") {
        print("_upload success!!!");
        return;
      }
    } finally {
      print(
          "upload isolate time : ${DateTime.now().millisecondsSinceEpoch - start}");
    }
  }

  Future<bool> event({
    required String eventId,
    required String eventType,
    required String traceId,
    String? extraInfo,
    int? costTime,
  }) async {
    EventInfo eventInfo = EventInfo(
      appid: appId,
      appName: packageInfo.appName,
      packageName: packageInfo.packageName,
      eventId: eventId,
      eventTime: DateTime.now().millisecondsSinceEpoch,
      eventType: eventType,
      costTime: costTime,
      traceId: traceId,
      platform: _getPlatform(),
      deviceInfo: _buildDeviceInfo(),
      appVersion: packageInfo.version,
      extraInfo: extraInfo,
      curPage: curPage,
      prePage: prePage,
      userId: userId,
      deviceId: deviceId,
      clientId: clientId,
    );
    return await _tracker(eventInfo);
  }

  String _buildDeviceInfo() {
    if (deviceInfo is AndroidDeviceInfo) {
      AndroidDeviceInfo androidDeviceInfo = deviceInfo;
      return {
        "device": androidDeviceInfo.device,
        "version": androidDeviceInfo.version.release,
      }.toString();
    }
    return "";
  }

  int _getPlatform() {
    if (Platform.isAndroid) {
      return 0;
    } else if (Platform.isIOS) {
      return 1;
    } else if (Platform.isWindows) {
      return 2;
    } else if (Platform.isMacOS) {
      return 3;
    } else {
      return 4;
    }
  }

  void read() {}

  void clear() {}

  Future<String?> getPlatformVersion() {
    return FlutterTrackerPlatform.instance.getPlatformVersion();
  }
}
